function createApp(config) {
  let app = {}
  app.mount = function (id) {
    /*
      mount 的作用是什么
        1.根据用户传入的选择器，就以这个选择器选中的元素作为宿主元素
          拿到宿主元素之后 拿到这个元素的 innerHTML 作为template
          通过编译的方式得到渲染函数 -- 根据渲染函数可以真正的得到DOM节点（再根据用户传入的状态数据最终生成）
        2.最终将render函数生成的DOM节点追加到宿主元素身上
    */
    this.rootDOM = document.getElementById(id)

    if (!config.render) {
      /*
        判断传入的配置项是是否有render方法 如果没有就自己通过编译生成render方法
      */
      config.render = this.compile(this.rootDOM.innerHTML)
    }

    if (config.setup) {
      this.setupCtx = config.setup()
    } else {
      this.dataCtx = config.data()
    }

    /*
      封装一个 proxy 对象代理， 相比于Object.defineProperty 性能更高，且可以代理代理至别的对象，有他可以很好的兼容vue2和vue3的api
    */
    this.proxy = new Proxy(this, {
      get(target, key) {
        if (key in target.setupCtx) {
          return target.setupCtx[key]
        } else {
          return target.dataCtx[key]
        }
      },
      set(target, key, value) {
        if (key in target.setupCtx) {
          target.setupCtx[key] = value
        } else {
          target.dataCtx[key] = value
        }
      }
    })

    /*
      这里把这个代理传进入 将来访问代理的某个值 就会进入代理的 get方法 
        get方法就会去判断是否又 setup api 如果有就用 setup api 没有就用 option api
    */
    const el = config.render.call(this.proxy)
    this.rootDOM.innerHTML = ''
    this.rootDOM.appendChild(el)
    return this
  }
  app.compile = function (template) {
    /*
      这个是个阉割版本的 生成写死的h3标签了
        
      尤大大完整版这里是会根据 template 去解析有什么dom，把DOM中的 {{title}} 替换成用户传入对象的数据的那个title
        目前只是为了理解mount的过程 所以就写一个阉割版本，但是这样写下来，已经能够很好的理解和明白 mount 函数的作用和意义了
    */
    return function () {
      const h3 = document.createElement('h3')
      h3.textContent = this.title
      return h3
    }
  }
  return app
}